今天, 是系列文的小小里程碑, 我們終於在今天找出了 task 的其中一種非同步演算法。
他被用在讓 Task 接著執行下一個任務的 method 中。
查看昨天提到執行這件事的 method ITaskCompletionAction.Invoke(this)
// Interface to which all completion actions must conform.
// This interface allows us to combine functionality and reduce allocations.
// For example, see Task.SetOnInvokeMres, and its use in Task.SpinThenBlockingWait().
// This code:
// ManualResetEvent mres = new ManualResetEventSlim(false, 0);
// Action<Task> completionAction = delegate { mres.Set() ; };
// AddCompletionAction(completionAction);
// gets replaced with this:
// SetOnInvokeMres mres = new SetOnInvokeMres();
// AddCompletionAction(mres);
// For additional examples of where this is used, see internal classes Task.SignalOnInvokeCDE,
// Task.WhenAllPromise, Task.WhenAllPromise<T>, TaskFactory.CompleteOnCountdownPromise,
// TaskFactory.CompleteOnCountdownPromise<T>, and TaskFactory.CompleteOnInvokePromise.
internal interface ITaskCompletionAction
{
void Invoke(Task completingTask);
}
發現其原來僅僅只是一個介面, 本身的功能是由他的繼承者實踐的,
此時非常巧合的(一點都不巧 XD )我想起了在 Day7 時提到的 Task.WhenAll
流程
當 task 未完成, 就把
WhenAllPromise
的 reference 掛載到 未完成的 task 的連續任務區(這個名詞是我自己取的 XD )
而在 WhenAllPromise
中恰好有一個 method 叫 Invoke
其功能是 atomic 的減少 WhenAllPromise
中未完成的任務數量, 且當數量歸零, 結束WhenAllPromise
和回傳結果。
然後又發現了, 原來 WhenAllPromise
正式繼承自ITaskCompletionAction
自此我們拚完了 WhenAllPromise
的最後一塊拼圖, 也解釋了讓 Task 接著執行下一個任務的方法。
接著會抽象的解釋其流程, 再利用 WhenAllPromise
做舉例。
抽象流程:
資料結構
當希望某個資料結構所要執行的事務, 可以被放到其他某個任務完成後執行時, 就將那個事務打包成名為 Invoke
的方法, 放在資料結構裡。
使用端 (假設 Thread 具有足夠承載力, 不需要把後續任務放入 TP , 可在本身 thread 繼續執行)
當調用某"資料結構"執行某事務, 發現該事務目前無法完成, 需要留到某其他任務完成後執行, 就將該資料結構推入 Task 中的連續任務區, 在 Task 將其本身的任務完成後, 會依序觸發 Task 的連續任務區裡的資料結構的 Invoke
方法。
值得注意的是每種 CASE 有其對應的的細節, 不過概略上的邏輯差不多, 所以我僅以其中一種為例。
實際流程舉例 :
Task.WhenAll
完整流程:
當 Task.WhenAll( taskList )
調用後執行動作
taskList = 待完成任務列表, 我自己命名的變數
WhenAllPromise
物件, 且傳入 taskListWhenAllPromise
物件中WhenAllPromise
內部執行依序掃描 taskList 內的 taskWhenAllPromise
有可能被掛載到別的 task 執行, 就會被視為 multi-thread )WhenAllPromise
的 reference 掛載到 未完成的 task 的連續任務區(這個名詞是我自己取的 XD )WhenAllPromise
內的未完成任務數量為0, 表示任務全部完成, 當即設置回傳變數, 與進行回傳。WhenAllPromise
有在上面的第 5 步掛載到別的 task。WhenAllPromise
不會結束。Task.WhenAll( taskList )
留在原地閒置。WhenAllPromise
掛載到的任務完成, 表示當初傳入的 taskList 的未完成任務數量又減了一所以在階段結束時執行被掛載的 WhenAllPromise
中的Invoke
方法。Invoke
方法會 atomic 的把未完成任務數量減 1 , 且若減完 1 未完成任務數量為 0 時, WhenAllPromise
當即設置回傳變數, 與進行回傳。Task.WhenAll( taskList )
順利回傳 taskList 執行結果, 主程式繼續往下運行。這真的是一種有很有趣的演算法, 當你以非同步的方式要進行某件任務, 卻發現這個任務被別的任務阻塞無法完成, 就直接把自己掛載到阻塞你的對象身上, 並且要求他在自己完成後, 接著執行掛載到自己身上的任務。
以上做法其實就是一種初步的 schedule 演算法, 就如同我在第二天的 sample , 一個 httpServer ,
可以發現我雖然創建了無數個 thread 來處理 request 但是大多數的 thread 其實只是在無限迴圈中空轉等待 request 的到來, 這其實非常耗費資源, 我當時提到, 應該要有種 schedule 的方法把沒在用的 thread 送入 閒置狀態, 而我們今天讀到的就是一種方法。
若是不使用這種演算法, 運行 Task.WhenAll( taskList )
的 thread 也需要進入一個無限迴圈, 需要不斷的檢測他追蹤的任務是否已經完成, 而如今, 直接將 thread 閒置, 讓每個任務完成時主動進行通知, 大大提高了效能。
明天, 輪到讓 Task 進入 TP 中的 method ThreadPool.UnsafeQueueCustomWorkItem
終於可以開始聊 TP 了!
明天見 !